---
title: PostGraphile JWT guide
---

JWTs are optional in PostGraphile. You can authenticate users with session
cookies, API keys, mTLS, or any other mechanism that lets your web framework
identify who is making the request. The key requirement is that you surface
the relevant details to PostgreSQL via
[`pgSettings`](./config.mdx#pgsettings) so that Row Level Security (RLS)
policies and functions can make decisions. See the
[security](./security.md) page for the broader architecture and trade-offs.

Assuming that you choose to use JWTs to provide authentication to PostGraphile
(and the author would encourage you to consider alternatives), this guide
explains how JWTs fit into that picture, how to populate `pgSettings` from a
verified token, and when you might lean on the `postgraphile/presets/lazy-jwt`
convenience preset.

:::info[You own JWT verification]

You are expected to verify tokens in your server framework (Express, Koa,
Fastify, etc.) and feed the resulting claims to PostGraphile via the
`preset.grafast.context` callback's `pgSettings` property. This means you can
implement refresh tokens, revocation, clock skew handling, and other behaviours
that PostGraphile cannot reasonably guess for you.

:::

## Typical request flow

1. Your web framework receives the request and runs whichever authentication
   middleware you have chosen (sessions, JWTs, mutual TLS, etc.).
2. After verification you extract the claims you want PostgreSQL to know about
   and pass them into `pgSettings` via `preset.grafast.context`.
3. PostgreSQL reads those values with `current_setting(...)` inside policies and
   functions, completing the authorization story.

For JWT-based setups the first step is often done with
[`jsonwebtoken`](https://github.com/auth0/node-jsonwebtoken), `jose`, or vendor
SDKs. The second step is where PostGraphile comes in.

## Populating `pgSettings` after verifying a JWT

Below is an Express-flavoured example that validates a Bearer token and exposes
select claims to PostgreSQL.

```ts title="graphile.config.ts"
import { PostGraphileAmberPreset } from "postgraphile/presets/amber";
import { verify } from "jsonwebtoken";

const preset: GraphileConfig.Preset = {
  extends: [PostGraphileAmberPreset],

  grafast: {
    async context(requestContext, args) {
      const req = requestContext.expressv4?.req;
      const header = req?.get("authorization") ?? "";
      const [, token] = header.split(" ");

      const pgSettings = {
        ...args.contextValue?.pgSettings,
      } as Record<string, string>;

      if (token) {
        try {
          const claims = verify(token, process.env.JWT_SECRET!, {
            audience: "postgraphile",
            algorithms: ["HS256"],
          });
          if (typeof claims === "object" && claims !== null) {
            if (claims.role && typeof claims.role === "string") {
              pgSettings.role = claims.role;
            }
            for (const [key, value] of Object.entries(claims)) {
              if (typeof value === "undefined" || value === null) continue;
              if (!/^[a-z_][a-z0-9_]*$/i.test(key) || key.length > 52) continue;
              pgSettings[`jwt.claims.${key}`] = String(value);
            }
          }
        } catch (e) {
          // Let the framework decide how to surface auth errors.
          requestContext.expressv4?.res?.status(401);
          throw e;
        }
      }

      return {
        ...args.contextValue,
        pgSettings,
      };
    },
  },
};

export default preset;
```

Key points:

- You decide which claims to trust and forward. You might strip or rename
  values, or merge in data from a session store.
- Anything placed inside `pgSettings` becomes available via
  `current_setting('...', true)` when RLS policies run.
- Do not leak entire JWT payloads blindly—only forward what your database schema needs.

If you want to understand how claims map onto PostgreSQL session variables, see
the [JWT specification](./jwt-specification.md) for the precise rules.

## Supplying JWTs from clients

Clients typically send JWTs via the `Authorization` header using the Bearer
scheme:

```ini
Authorization: Bearer JWT_TOKEN_HERE
```

Below are a few JavaScript client examples; adapt them to your own stack.

### Apollo Client (v3, HTTP)

```js
const httpLink = createHttpLink({
  uri: "/graphql",
});

const authLink = setContext((_, { headers }) => {
  const token = getJWTToken();
  return {
    headers: {
      ...headers,
      ...(token ? { authorization: `Bearer ${token}` } : null),
    },
  };
});

const client = new ApolloClient({
  link: authLink.concat(httpLink),
  cache: new InMemoryCache(),
});
```

If you are using Apollo Client 3 with `graphql-ws` for subscriptions:

```js
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";

const token = getJWTToken();

const wsLink = new GraphQLWsLink(
  createClient({
    url: "ws://localhost:3000/graphql",
    connectionParams: token
      ? { authorization: `Bearer ${token}` }
      : {},
  }),
);
```

See the [Apollo authentication docs][apollo-auth] and the
[subscriptions guide][apollo-subscriptions] for additional configuration
options.

### Relay (Modern)

```js
function fetchQuery(operation, variables) {
  const token = getJWTToken();
  return fetch("/graphql", {
    method: "POST",
    headers: {
      "content-type": "application/json",
      authorization: token ? `Bearer ${token}` : "",
    },
    body: JSON.stringify({
      query: operation.text,
      variables,
    }),
  }).then((response) => response.json());
}

const environment = new Environment({
  network: Network.create(fetchQuery),
  store: new Store(new RecordSource()),
});
```

Relay’s network layer setup is documented in the
[Relay guides](https://relay.dev/docs/guides/network-layer/).

[apollo-auth]:
  https://www.apollographql.com/docs/react/networking/authentication/
[apollo-subscriptions]:
  https://www.apollographql.com/docs/react/data/subscriptions/

## About the `lazy-jwt` preset

`postgraphile/presets/lazy-jwt` exists for teams that need a quick stopgap while
building a real authentication layer. When enabled it:

- runs only when you use a `grafserv` HTTP adaptor;
- reads the `Authorization` header for Bearer tokens from
  `requestContext.node?.req?.headers?.authorization` (thus only works with
  adaptors that populate this, such as `postgraphile/grafserv/express/v4`);
- verifies the token with `jsonwebtoken` using a shared secret (set via
  `preset.grafserv.pgJwtSecret` or `preset.schema.pgJwtSecret`), default
  algorithms `HS256`/`HS384`, and default audience `postgraphile` (override via
  `preset.grafserv.pgJwtVerifyOptions`);
- copies the `role` claim and alphanumeric claims into `pgSettings` under the
  `jwt.claims.*` namespace.

It does **not** handle refresh tokens, key rotation, token revocation, custom
claim mapping, or multi-tenant secret lookup. Treat it as a temporary measure
and plan to replace it with middleware that explicitly sets `pgSettings`.

## Issuing JWTs from PostgreSQL

If you want to generate JWTs from your PostgreSQL schema, PostGraphile can sign
composite types listed in `preset.gather.pgJwtTypes`. When a function returns
one of those types the response will instead include a signed JWT string (signed
using the `preset.schema.pgJwtSecret` and any `preset.schema.pgJwtSignOptions`).
Combine that with your own authentication SQL to issue tokens. Remember that
this feature does not manage refresh tokens or provide a complete auth system—
it only signs the composite payloads you return.

## Choosing between JWTs and alternatives

JWTs are one option amongst many. Session cookies, OAuth-backed passport
strategies, or bespoke API keys all work as long as you populate `pgSettings`
appropriately. The
[`pgSettings` section of the config docs](./config.mdx#pgsettings)
shows how to expose request data regardless of authentication style, and the
[security](./security.md) page lists common approaches. Pick the strategy that
fits your infrastructure, and make sure you're aware of the trade-offs of each
approach.
